package transaction

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"publicChainSystem/tools"
	"publicChainSystem/wallet"
	"time"
)

/**
 *@author:zhengyilong
 *@email:1797344574@qq.com
 *@phone:13479394730
 *@DateTime:2022/4/18 9:42
 **/

type Transaction struct {
	//交易的唯一标识
	Txid []byte
	//多个交易输出
	Output []Output
	//多个交易输入
	Input []Input

	//时间戳，//加时间戳，防止同一个用户发起多次的coinbase交易的交易哈希一样
	TimeStamp int64
}

/*
序列化	把目标结构体转成一个有序的序列
	json	xml
*/
func (transaction *Transaction) Serialize() ([]byte,error){
	var result bytes.Buffer
	en := gob.NewEncoder(&result)
	err := en.Encode(transaction)
	if err != nil {
		return nil,err
	}
	return result.Bytes(),nil
}

//创建一个普通交易
func NewTransaction(from,to string,amout uint,spendableOutputs []UTXO)(*Transaction,error) {
	//要买一个70元的东西，先把余额中的每一笔钱进行累加，找到刚刚好够70元的时候就可以了
	//不需要把所有的余额都用上
	
		//c.根据找到的交易输出，构建input
		inputs := make([]Input,0)
	for _, output := range spendableOutputs {
		//构建交易输入就要引用交易输出，因为交易输入本质就是之前历史交易中的未消费的交易输出
		//不光需要公钥，签名需要私钥来完成，所以还需要私钥，需要把私钥存起来
		//用boltdb数据库，key->value
		//				地址 -> 对应地址的私钥
		input := NewInput(output.Txid,output.Index,nil,nil)
		inputs = append(inputs, input)
	}
	//2.构建output（交易输出）
	var spendAmount uint	//用来记录，到此次为止，一共花费了多少钱
	outputs := make([]Output,0)
	for _, out := range spendableOutputs {
		spendAmount += out.Value
		if spendAmount <= amout {
			//根据地址求出对应公钥哈希，用于构建output
			pubHash, err := wallet.GetPubHash(to)
			if err != nil {
				return nil, err
			}

			//余额够本次的交易，没有找零
			output := NewOutput(out.Value,pubHash)
			outputs = append(outputs,output)
		}else {
			//需要找零
			spendAmount -= out.Value
			//本次金额要凑的钱
			needAmount := amout - spendAmount

			//根据to地址求出to的公钥哈希，用于构建output
			to_pubHash, err := wallet.GetPubHash(to)
			if err != nil {
				return nil, err
			}

			output := NewOutput(needAmount,to_pubHash)
			outputs = append(outputs,output)

			//根据from地址求出from的公钥哈希，用于构建output
			from_pubHash, err := wallet.GetPubHash(from)
			if err != nil {
				return nil,err
			}

			//找零
			backchange := NewOutput(out.Value - needAmount, from_pubHash)
			outputs = append(outputs,backchange)
		}
	}
	//3.给Txid赋值，并返回
	tx := Transaction{
		Output: outputs,
		Input: inputs,
		//因为coinbase交易需要用到时间戳，所以在此也需同步使用
		TimeStamp: time.Now().Unix(),
	}
	txBytes, err := tx.Serialize()
	if err != nil {
		fmt.Printf(err.Error())
		return nil,err
	}
	hash := tools.GetHash(txBytes)
	tx.Txid = hash
	return &tx,nil
}

//创建一个Coinbase交易
func NewCoinbase(address string)(*Transaction,error) {
	//根据地址求出公钥哈希，存入output中去
	pubHash, err := wallet.GetPubHash(address)
	if err != nil {
		return nil, err
	}

	cd := Transaction{
		Output: []Output{
			{
				Value: 50,
				ScriptPubkey:pubHash,
			},
		},
		Input: nil,
		//加时间戳，防止同一个用户发起多次的coinbase交易的交易哈希一样
		TimeStamp: time.Now().Unix(),
	}
	txBytes, err := cd.Serialize()
	if err != nil {
		return nil,err
	}
	hash := tools.GetHash(txBytes)
	cd.Txid = hash
	return &cd,nil
}